Bu bölümde, Windows işletim sisteminde C Programlama Dili ile ağ iletişimi hakkında bilgi vermeye çalışacağız.
Soket
Soket, ağ üzerinde iletişim kuran, aynı veya farklı makinelerdeki, iki programın (process) arasındaki bağlantının her iki ucundaki soyut bir noktadır. İki bilgisayar arasındaki veri alışverişini sağlayan bir "kapı" veya "uç nokta" olarak düşünebilir. Ağ iletişimi için ise bir ağ bağlantısı ve iki soket gerekir.
Soket, bir IP adresi ve bir port numarasının birleşiminden oluşur. Bu sayede, doğru bilgisayardaki (IP adresi) doğru uygulamayı (port numarası) hedefleyebilirsiniz.
Soketlerin çalışması (Sunucu-İstemci Modeli)
Soketler genellikle sunucu (server) ve istemci (client) modeliyle çalışır. Süreç şu şekilde işler:
Soket türleri
Temel olarak iki ana soket türü vardır ve kullanılacak iletişim türüne göre seçilirler:
1. TCP Soketleri (Akış Soketleri - SOCK_STREAM)
2. UDP Soketleri (Veri Bloğu Soketleri - SOCK_DGRAM)
* bind(), listen() ve accept() fonksiyonları sadece sunucuda kullanılır.
* connect() fonksiyonu sadece istemcide kullanılır.
Windows işletim sistemlerinde ağ iletişimi için Windows Sockets API (Winsock) kullanılır. Winsock, Windows uygulamalarının TCP/IP ve diğer ağ protokollerini kullanarak iletişim kurmasını sağlayan bir programlama arayüzüdür (API).
Temel amacı, uygulama geliştiricilerin, işletim sisteminin çekirdek düzeyindeki karmaşık ağ detaylarına girmeden ağ üzerinden veri gönderip alabilmelerini sağlamaktır.
Winsock, uygulamaların aşağıdakileri yapmasına olanak tanır:
Sunucuda yapılan işlemler aşağıdaki sıra ile gerçekleştirilmektedir:
1. Winsock başlatma
Winsock fonksiyonlarını çağıran tüm işlemler (uygulamalar veya DLL'ler), diğer Winsock fonksiyonlarını çağırmadan önce Windows Sockets DLL'sini başlatmalıdır. Bu aynı zamanda Winsock'un sistemde desteklendiğinden de emin olmamızı sağlar.
Winsock'u başlatmak için, wsaData adında bir WSADATA nesnesi oluşturmalı ve WSAStartup fonksiyonunu bu nesne ile çağırarak geri dönüş değerini bir hata durumu için kontrol etmeliyiz.
WSADATA wsaData; // Windows Sockets uygulaması hakkında bilgi içerir.
int iResult;
// Winsock başlatma
iResult = WSAStartup(MAKEWORD(2,2), &wsaData); // WS2_32.dll'nin kullanımını başlatmak için çağrılır.
if (iResult != 0) {
printf("WSAStartup hatası: %d\n", iResult);
return 1;
}
2. Sunucu için soket oluşturma
Sunucuda kullanılmak üzere bir soket oluşturmak için,
#define DEFAULT_PORT "8080"
struct addrinfo *result = NULL, *ptr = NULL, hints;
ZeroMemory(&hints, sizeof (hints));
hints.ai_family = AF_INET;
hints.ai_socktype = SOCK_STREAM;
hints.ai_protocol = IPPROTO_TCP;
hints.ai_flags = AI_PASSIVE;
// Sunucu tarafından kullanılan lokal adres ve bağlantı noktası bilgilerini alır.
iResult = getaddrinfo(NULL, DEFAULT_PORT, &hints, &result);
if (iResult != 0) {
printf("getaddrinfo hatası: %d\n", iResult);
WSACleanup();
return 1;
}
SOCKET ListenSocket = INVALID_SOCKET;
ListenSocket = socket(result->ai_family, result->ai_socktype, result->ai_protocol);
if (ListenSocket == INVALID_SOCKET) {
printf("Error at socket(): %ld\n", WSAGetLastError());
freeaddrinfo(result);
WSACleanup();
return 1;
}
3. Soketi bağlama
Bir sunucunun istemci bağlantılarını kabul edebilmesi için sistem içindeki bir ağ adresine bağlı olması gerekir. İstemci uygulamaları, ana bilgisayar ağına bağlanmak için IP adresini ve bağlantı noktasını kullanır.
Bir soketi bağlamak için,
// TCP dinleme soketini oluşturma
iResult = bind(ListenSocket, result->ai_addr, (int)result->ai_addrlen);
if (iResult == SOCKET_ERROR) {
printf("bind hatası: %d\n", WSAGetLastError());
freeaddrinfo(result);
closesocket(ListenSocket);
WSACleanup();
return 1;
}
freeaddrinfo(result);
4. Soketi dinleme
Soket, sistemdeki bir IP adresine ve bağlantı noktasına bağlandıktan sonra, sunucu gelen bağlantı istekleri için bu IP adresini ve bağlantı noktasını dinlemelidir.
Bir soketi dinlemek için, Listen() fonksiyonunu çağırın ve parametre olarak oluşturulan soketi ve bekleme listesi değerini, kabul edilecek bekleyen bağlantıların maksimum uzunluğunu belirtin.
if (listen( ListenSocket, SOMAXCONN ) == SOCKET_ERROR) {
printf("Listen hatası: %ld\n", WSAGetLastError());
closesocket(ListenSocket);
WSACleanup();
return 1;
}
5. Bağlantı kabul etme
Soket bir bağlantıyı dinlemeye başladıktan sonra, program o soket üzerindeki bağlantı isteklerine işlem yapmalıdır.
Normalde bir sunucu uygulaması, birden fazla istemciden gelen bağlantıları dinleyecek şekilde tasarlanır. Yüksek performanslı sunucularda, birden fazla istemci bağlantısını işlemek için genellikle birden fazla iş parçacığı (thread) kullanılır.
Bir sokete bağlantı kabul etmek için,
SOCKET ClientSocket;
ClientSocket = INVALID_SOCKET;
// Bir istemci soketini kabul etme
ClientSocket = accept(ListenSocket, NULL, NULL);
if (ClientSocket == INVALID_SOCKET) {
printf("accept hatası: %d\n", WSAGetLastError());
closesocket(ListenSocket);
WSACleanup();
return 1;
}
closesocket(ListenSocket);
accept() fonksiyonu bloke edici (blocking) bir fonksiyondur. Bir istemci bağlantısı gelene kadar programın akışını durdurur ve bekler. Bu süre boyunca program accept() satırında "takılır" ve sonraki satırlara geçmez.
6. Sunucuda veri gönderme ve alma
Sunucu veri gönderip almak için send() ve recv() fonksiyonlarını kullanır. Her iki fonksiyon, gönderilen veya alınan bayt sayısını veya bir hatayı tam sayı olarak döndürür.
#define DEFAULT_BUFLEN 512
char recvbuf[DEFAULT_BUFLEN];
int iResult, iSendResult;
int recvbuflen = DEFAULT_BUFLEN;
// İstemci bağlantıyı kapatana kadar al
do {
iResult = recv(ClientSocket, recvbuf, recvbuflen, 0);
if (iResult > 0) {
printf("Alınan byte sayısı: %d\n", iResult);
// Tampon belleği gönderene yollar.
iSendResult = send(ClientSocket, recvbuf, iResult, 0);
if (iSendResult == SOCKET_ERROR) {
printf("send hatası: %d\n", WSAGetLastError());
closesocket(ClientSocket);
WSACleanup();
return 1;
}
printf("Gönderilen byte sayısı: %d\n", iSendResult);
}
else if (iResult == 0)
printf("Bağlantı kapanıyor...\n");
else {
printf("recv hatası: %d\n", WSAGetLastError());
closesocket(ClientSocket);
WSACleanup();
return 1;
}
} while (iResult > 0);
7. Sunucu bağlantısını kesme
Sunucu, istemciden veri almayı ve istemciye veri göndermeyi tamamladıktan sonra, istemciden bağlantıyı keser ve soketi kapatır.
Bir soketi ayırmak ve kapatmak için,
iResult = shutdown(ClientSocket, SD_SEND);
if (iResult == SOCKET_ERROR) {
printf("shutdown failed: %d\n", WSAGetLastError());
closesocket(ClientSocket);
WSACleanup();
return 1;
}
closesocket(ClientSocket);
WSACleanup();
return 0;
İstemcide yapılan işlemler aşağıdaki sıra ile gerçekleştirilmektedir:
1. İstemci için bir soket oluşturma
Başlatma işleminden sonra, istemci tarafından kullanılmak üzere bir SOCKET nesnesi oluşturulmalıdır.
Bir soket oluşturmak için,
struct addrinfo *result = NULL,
*ptr = NULL,
hints;
ZeroMemory(&hints, sizeof(hints));
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
hints.ai_protocol = IPPROTO_TCP;
#define DEFAULT_PORT "8080"
// Sunucu adresini ve bağlantı noktası bilgilerini alır.
iResult = getaddrinfo(argv[1], DEFAULT_PORT, &hints, &result);
if (iResult != 0) {
printf("getaddrinfo failed: %d\n", iResult);
WSACleanup();
return 1;
}
SOCKET ConnectSocket = INVALID_SOCKET;
ptr = result;
// Sunucuya bağlanmak için bir SOKET oluşturur.
ConnectSocket = socket(ptr->ai_family, ptr->ai_socktype, ptr->ai_protocol);
if (ConnectSocket == INVALID_SOCKET) {
printf("socket hatası: %ld\n", WSAGetLastError());
freeaddrinfo(result);
WSACleanup();
return 1;
}
2. Bir sokete bağlanma
Bir istemcinin ağ üzerinde iletişim kurabilmesi için bir sunucuya bağlanması gerekir.
Bir sokete bağlanmak için, oluşturulan soketi ve sockaddr yapısını parametre olarak geçirerek connect() fonksiyonunu çağırın. Genel hataları kontrol edin.
// Sunucuya bağlan
iResult = connect( ConnectSocket, ptr->ai_addr, (int)ptr->ai_addrlen);
if (iResult == SOCKET_ERROR) {
closesocket(ConnectSocket);
ConnectSocket = INVALID_SOCKET;
}
freeaddrinfo(result);
if (ConnectSocket == INVALID_SOCKET) {
printf("Sunucuya bağlanamıyor!\n");
WSACleanup();
return 1;
}
getaddrinfo() fonksiyonu, sockaddr yapısındaki değerleri belirlemek için kullanılır. sockaddr yapısında istemcinin bağlanmaya çalışacağı sunucunun IP adresi ile istemcinin bağlanacağı sunucudaki port numarası yer alır.
3. İstemcide veri gönderme ve alma
İstemci veri gönderip almak için send() ve recv() fonksiyonlarını kullanır. Her iki fonksiyon, gönderilen veya alınan bayt sayısını veya bir hatayı tam sayı olarak döndürür.
#define DEFAULT_BUFLEN 512
int recvbuflen = DEFAULT_BUFLEN;
const char *sendbuf = "Bu bir test mesajıdır!";
char recvbuf[DEFAULT_BUFLEN];
int iResult;
iResult = send(ConnectSocket, sendbuf, (int) strlen(sendbuf), 0);
if (iResult == SOCKET_ERROR) {
printf("send hatası: %d\n", WSAGetLastError());
closesocket(ConnectSocket);
WSACleanup();
return 1;
}
printf("Gönderilen byte sayısı: %ld\n", iResult);
iResult = shutdown(ConnectSocket, SD_SEND);
if (iResult == SOCKET_ERROR) {
printf("shutdown hatası: %d\n", WSAGetLastError());
closesocket(ConnectSocket);
WSACleanup();
return 1;
}
do {
iResult = recv(ConnectSocket, recvbuf, recvbuflen, 0);
if (iResult > 0)
printf("Alınan byte sayısı: %d\n", iResult);
else if (iResult == 0)
printf("Bağlantı kapandı!\n");
else
printf("recv hatası: %d\n", WSAGetLastError());
} while (iResult > 0);
4. İstemci bağlantısını kesme
İstemci veri gönderme ve alma işlemini tamamladığında sunucudan bağlantısını keser ve soketi kapatır.
Bir soketi ayırmak ve kapatmak için,
iResult = shutdown(ConnectSocket, SD_SEND);
if (iResult == SOCKET_ERROR) {
printf("shutdown hatası: %d\n", WSAGetLastError());
closesocket(ConnectSocket);
WSACleanup();
return 1;
}
closesocket(ConnectSocket);
WSACleanup();
return 0;
Aşağıda, TCP/IP Sunucu ve İstemci uygulamalarının kaynak kodunu yer almaktadır.
Sunucu uygulaması
İstemci uygulaması
client localhost
#include <winsock2.h>
#include <windows.h>
#include <ws2tcpip.h>
#include <stdlib.h>
#include <stdio.h>
#include <locale.h>
#define DEFAULT_BUFLEN 512
#define DEFAULT_PORT "8080"
int main(void)
{
WSADATA wsaData;
int iResult;
// Yerel ayarları Türkçe'ye çevirme
setlocale(LC_ALL, "Turkish");
system("chcp 1254 >nul");
SOCKET ListenSocket = INVALID_SOCKET;
SOCKET ClientSocket = INVALID_SOCKET;
struct addrinfo *result = NULL;
struct addrinfo hints;
int iSendResult;
char recvbuf[DEFAULT_BUFLEN];
int recvbuflen = DEFAULT_BUFLEN;
// Winsock başlatma
iResult = WSAStartup(MAKEWORD(2,2), &wsaData);
if (iResult != 0) {
printf("WSAStartup hatası: %d\n", iResult);
return 1;
}
ZeroMemory(&hints, sizeof(hints));
hints.ai_family = AF_INET;
hints.ai_socktype = SOCK_STREAM;
hints.ai_protocol = IPPROTO_TCP;
hints.ai_flags = AI_PASSIVE;
// Sunucu adresi ve bağlantı noktası bilgilerini alma
iResult = getaddrinfo(NULL, DEFAULT_PORT, &hints, &result);
if (iResult != 0) {
printf("getaddrinfo hatası: %d\n", iResult);
WSACleanup();
return 1;
}
// Sunucunun istemci bağlantılarını dinlemesi için bir SOCKET oluşturma
ListenSocket = socket(result->ai_family, result->ai_socktype, result->ai_protocol);
if (ListenSocket == INVALID_SOCKET) {
printf("socket hatası: %d\n", WSAGetLastError());
freeaddrinfo(result);
WSACleanup();
return 1;
}
// TCP dinleme soketi oluşturma
iResult = bind(ListenSocket, result->ai_addr, (int)result->ai_addrlen);
if (iResult == SOCKET_ERROR) {
printf("bind hatası: %d\n", WSAGetLastError());
freeaddrinfo(result);
closesocket(ListenSocket);
WSACleanup();
return 1;
}
freeaddrinfo(result);
iResult = listen(ListenSocket, SOMAXCONN);
if (iResult == SOCKET_ERROR) {
printf("listen hatası: %d\n", WSAGetLastError());
closesocket(ListenSocket);
WSACleanup();
return 1;
}
printf("=== Sunucusu başlatıldı... ===\n");
printf("Port: %s\n", DEFAULT_PORT);
printf("Bağlantı bekleniyor...\n");
// İstemci soketini kabul etme
ClientSocket = accept(ListenSocket, NULL, NULL);
if (ClientSocket == INVALID_SOCKET) {
printf("accept hatası: %d\n", WSAGetLastError());
closesocket(ListenSocket);
WSACleanup();
return 1;
}
// Sunucu soketini kapatma
closesocket(ListenSocket);
// İstemci bağlantıyı kapatana kadar alma işlemi devam eder.
do {
iResult = recv(ClientSocket, recvbuf, recvbuflen, 0);
if (iResult > 0) {
printf("Alınan byte sayısı: %d\n", iResult);
// Alınan veriyi istemciye geri yollama
iSendResult = send(ClientSocket, recvbuf, iResult, 0);
if (iSendResult == SOCKET_ERROR) {
printf("send hatası: %d\n", WSAGetLastError());
closesocket(ClientSocket);
WSACleanup();
return 1;
}
printf("Gönderilen byte sayısı: %d\n", iSendResult);
}
else if (iResult == 0)
printf("Bağlantı kapanıyor...\n");
else {
printf("recv hatası: %d\n", WSAGetLastError());
closesocket(ClientSocket);
WSACleanup();
return 1;
}
} while (iResult > 0);
// İstemci soketi üzerindeki gönderme ve alma işlemlerini devre dışı bırakır.
iResult = shutdown(ClientSocket, SD_SEND);
if (iResult == SOCKET_ERROR) {
printf("shutdown hatası: %d\n", WSAGetLastError());
closesocket(ClientSocket);
WSACleanup();
return 1;
}
// Temizleme işlemi
closesocket(ClientSocket);
WSACleanup();
return 0;
}
Yukarıdaki programı derleyip çalıştırdığımızda, aşağıdaki ifadeleri ekrana yazar ve istemciden bağlantı gerçekleşene kadar bekler:
=== Sunucusu başlatıldı... === Port: 8080 Bağlantı bekleniyor..
İstemci bağlandıktan sonra, aşağıdaki ifadeleri ekrana yazar ve program sona erer.
Alınan byte sayısı: 24 Gönderilen byte sayısı: 24 Bağlantı kapanıyor...
#include <winsock2.h>
#include <windows.h>
#include <ws2tcpip.h>
#include <stdlib.h>
#include <stdio.h>
#include <locale.h>
#define DEFAULT_BUFLEN 512
#define DEFAULT_PORT "8080"
int main(int argc, char **argv)
{
WSADATA wsaData;
SOCKET ConnectSocket = INVALID_SOCKET;
struct addrinfo *result = NULL, *ptr = NULL, hints;
const char *sendbuf = "Bu bir deneme mesajıdır!";
char recvbuf[DEFAULT_BUFLEN];
int iResult;
int recvbuflen = DEFAULT_BUFLEN;
setlocale(LC_ALL, "Turkish");
system("chcp 1254 >nul");
// Validate the parameters
if (argc != 2) {
// Client.exe localhost veya Client.exe 127.0.0.1
printf("Kullanım: %s server-name (Client 127.0.0.1)\n", argv[0]);
return 1;
}
// Winsock başlatma
iResult = WSAStartup(MAKEWORD(2,2), &wsaData);
if (iResult != 0) {
printf("WSAStartup hatası: %d\n", iResult);
return 1;
}
ZeroMemory(&hints, sizeof(hints));
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
hints.ai_protocol = IPPROTO_TCP;
// Sunucu adresi ve bağlantı noktası bilgilerini alma
iResult = getaddrinfo(argv[1], DEFAULT_PORT, &hints, &result);
if (iResult != 0) {
printf("getaddrinfo hatası: %d\n", iResult);
WSACleanup();
return 1;
}
// Başarılı olana kadar bir adrese bağlanmaya çalışır.
for(ptr=result; ptr != NULL; ptr=ptr->ai_next) {
// Sunucuya bağlanmak için bir SOKET oluşturma
ConnectSocket = socket(ptr->ai_family, ptr->ai_socktype,
ptr->ai_protocol);
if (ConnectSocket == INVALID_SOCKET) {
printf("socket hatası: %d\n", WSAGetLastError());
WSACleanup();
return 1;
}
// Sunucuya bağlanma
iResult = connect(ConnectSocket, ptr->ai_addr, (int)ptr->ai_addrlen);
if (iResult == SOCKET_ERROR) {
closesocket(ConnectSocket);
ConnectSocket = INVALID_SOCKET;
continue;
}
break;
}
freeaddrinfo(result);
if (ConnectSocket == INVALID_SOCKET) {
printf("Sunucuya bağlanamıyor!\n");
WSACleanup();
return 1;
}
// İlk bellek içeriğini gönderme
iResult = send(ConnectSocket, sendbuf, (int)strlen(sendbuf), 0);
if (iResult == SOCKET_ERROR) {
printf("send hatası: %d\n", WSAGetLastError());
closesocket(ConnectSocket);
WSACleanup();
return 1;
}
printf("Gönderilen byte sayısı: %d\n", iResult);
// Bağlantıyı kapatma
iResult = shutdown(ConnectSocket, SD_SEND);
if (iResult == SOCKET_ERROR) {
printf("shutdown hatası: %d\n", WSAGetLastError());
closesocket(ConnectSocket);
WSACleanup();
return 1;
}
// Karşı taraf bağlantıyı kapatana kadar alma işlemi
do {
iResult = recv(ConnectSocket, recvbuf, recvbuflen, 0);
if (iResult > 0)
printf("Alınan byte sayısı: %d\n", iResult);
else if (iResult == 0)
printf("Bağlantı kapatıldı\n");
else
printf("recv hatası: %d\n", WSAGetLastError());
} while(iResult > 0);
// Temizleme işlemi
closesocket(ConnectSocket);
WSACleanup();
return 0;
}
Yukarıdaki programı derleyip çalıştırdığımızda (client localhost), aşağıdaki ifadeleri ekrana yazar ve program sona erer.
Gönderilen byte sayısı: 24 Alınan byte sayısı: 24 Bağlantı kapatıldı